package com.jenkins.test.security;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.jenkins.test.config.JwtConfig;
import com.jenkins.test.config.JwtSkipProperties;
import com.jenkins.test.response.Result;
import com.jenkins.test.utils.JacksonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @version V1.0.0
 * @ClassName: {@link OncePerRequestFilter}
 * @Description: 全局过滤器
 * @author: Lanzhou
 * @date: 2021/6/25 11:33
 * @Copyright:2020 All rights reserved.
 */
@Slf4j
@Component("globalSecurityFilter")
public class GlobalSecurityFilter extends OncePerRequestFilter {

    @Resource
    private JwtConfig jwtConfig;

    @Resource
    private LoginUserStorage loginUserStorage;

    /**
     * 配置忽略路径
     */
    @Resource
    private JwtSkipProperties jwtSkipProperties;

    /**
     * Uri 路径匹配组件
     */
    protected PathMatcher pathMatcher = new AntPathMatcher();


    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        String requestUri = httpServletRequest.getRequestURI();
        log.info("当前请求路径为: [ {} ] ", requestUri);
        // 忽略路径直接放行
        for (String skipUri : jwtSkipProperties.getSkipUrls()) {
            if (pathMatcher.match(skipUri, requestUri)) {
                filterChain.doFilter(httpServletRequest, httpServletResponse);
                return;
            }
        }

        String header = httpServletRequest.getHeader(jwtConfig.getHeader());
        if (StringUtils.isBlank(header)) {
            Result<String> unauthorized = Result.unauthorized("未携带授权信息, 请登入获取后重试");
            responseFlush(httpServletResponse, unauthorized);
        } else {
            // 认证和授权
            LoginUser loginUser = certification(header);
            if (null != loginUser) {
                try {
                    // 设置用户, 在接下来的时间中都可以获取到用户信息
                    SecurityContext.set(loginUser);
                    filterChain.doFilter(httpServletRequest, httpServletResponse);
                } finally {
                    // 用完清除域信息
                    SecurityContext.clear();
                }
            } else {
                Result<String> unauthorized = Result.error("无法识别的授权信息, 请重新授权后尝试");
                responseFlush(httpServletResponse, unauthorized);
            }
        }
    }

    /**
     * 输出给前台
     *
     * @param response
     * @param body
     * @throws IOException
     */
    private void responseFlush(HttpServletResponse response, Result<String> body) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        PrintWriter writer = response.getWriter();
        String json = JacksonUtil.objToJson(body);
        assert json != null;
        writer.write(json);
    }

    /**
     * 授权和认证, 失败返回null
     *
     * @param token
     * @return
     */
    private LoginUser certification(String token) {
        DecodedJWT verify = null;
        try {
            verify = jwtConfig.verify(token);
        } catch (Exception e) {
            log.error("解析Token出错", e);
            return null;
        }
        String key = verify.getClaim(SecurityConstant.TOKEN_KEY).asString();
        LoginUser loginUser = loginUserStorage.getLoginUser(key);
        if (null == loginUser) {
            return null;
        }
        long expire = loginUser.getExpire();
        // 当现在时间超过过期时间了则直接让他退出登入, 并且返回null
        if (System.currentTimeMillis() > expire) {
            loginUserStorage.deleteLoginUser(key);
            return null;
        }
        return loginUser;
    }


}
